using System;
using System.Data.SqlClient;
using System.Text.RegularExpressions;

namespace Tyng.Data.SqlServer
{

    class SqlExceptionInfo
    {
        private SqlExceptionType _type;
        private SqlException _exception;
        private ConstraintInfo _ci;

        public SqlExceptionInfo(SqlException exception)
        {
            if (exception == null) throw new ArgumentNullException("exception");

            _type = SqlExceptionType.Unknown;
            _exception = exception;

            switch (exception.Number)
            {
                case 547:
                case 2627:
                    SetConstraintInfo();
                    _type = SqlExceptionType.ConstraintViolation;
                    break;
            }

            if (_type == SqlExceptionType.Unknown && exception.Number >= 50000)
                _type = SqlExceptionType.UserDefined;
        }

        /// <summary>
        /// Flag indicating the severity of the error >= 20, a "Fatal" error in Sql Server.
        /// </summary>
        public bool IsFatal { get { return _exception.Class >= 20; } }
        public bool IsConstraintViolation { get { return SqlExceptionType == SqlExceptionType.ConstraintViolation; } }

        public ConstraintType ConstraintType 
        { 
            get 
            {
                if (_type != SqlExceptionType.ConstraintViolation)
                    throw new InvalidOperationException();

                return _ci.ConstraintType; 
            } 
        }
        public string ConstraintName 
        { 
            get 
            {
                if (_type != SqlExceptionType.ConstraintViolation)
                    throw new InvalidOperationException(); 
                
                return _ci.ConstraintName;
            }
        }
        
        public SqlExceptionType SqlExceptionType
        {
            get 
            {
                return _type;
            }
        }

        private sealed class ConstraintInfo
        {
            public ConstraintType ConstraintType = ConstraintType.Unknown;
            public string ConstraintName = string.Empty;
        }

        private void SetConstraintInfo()
        {
            string pattern;
            _ci = new ConstraintInfo();

            switch(_exception.Number)
            {
                case 547:
                    pattern = "The (?<StatementType>.+) statement conflicted with the (?<ConstraintType>.+) constraint \"(?<ConstraintName>.+)\"\\. The conflict occurred in database \"(?<DatabaseName>.+)\", table \"(?<TableName>.+)\".*";
                    break;
                case 2627:
                    pattern = "Violation of (?<ConstraintType>.+) constraint '(?<ConstraintName>.+)'. Cannot insert duplicate key in object '(?<ObjectName>.+)'\\.";
                    break;
                default:
                    return;
            }

            Match m = Regex.Match(_exception.Message, pattern);

            if (!m.Success)
                return;

            string type = m.Groups["ConstraintType"].Value;

            switch (type)
            {
                case "CHECK":
                    _ci.ConstraintType = ConstraintType.Check;
                    break;
                case "PRIMARY KEY":
                    _ci.ConstraintType = ConstraintType.PrimaryKey;
                    break;
                case "FOREIGN KEY":
                    _ci.ConstraintType = ConstraintType.ForeignKey;
                    break;
            }

            _ci.ConstraintName = m.Groups["ConstraintName"].Value;

        }
    }
}
